package com.ym.medical.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.thread.GlobalThreadPool;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSONObject;
import com.qiniu.storage.model.BatchStatus;
import com.qiniu.util.StringMap;
import com.ym.core.config.ConfigCenterProperties;
import com.ym.core.config.FileTableConfig;
import com.ym.core.config.QiNiuYunConfig;
import com.ym.core.exception.BusinessException;
import com.ym.core.utils.qiniu.QNKeyEnum;
import com.ym.core.utils.qiniu.QNResponse;
import com.ym.core.utils.qiniu.QiNiuUploadUtil;
import com.ym.core.utils.qiniu.YamiUpPutPolicy;
import com.ym.core.utils.tkmybatis.TK;
import com.ym.medical.dao.FileInforDao;
import com.ym.medical.domain.entity.FileInforEntity;
import com.ym.medical.enums.FileTypeEnum;
import com.ym.medical.service.ICommonSV;
import com.ym.medical.service.IFileInforSV;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.helper.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 文件信息Service业务层处理
 *
 * @author ym
 * @date 2020-08-17
 */
@Service
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class FileInforSVImpl implements IFileInforSV {

	@Autowired
	private FileInforDao mapper;

	@Autowired
	private ConfigCenterProperties configCenterProperties;

	@Value("${server.servlet.context-path}")
	private String appName;

	@Autowired
	private FileTableConfig fileTableConfig;

	@Autowired
	private ICommonSV commonSV;

	@Override
	public FileInforEntity selectByStringId(String id) {
		return mapper.selectByPrimaryKey(id);
	}

	@Override
	public String uploadFile(MultipartFile file, FileTypeEnum fileType, String table, String field, String remark, Long upPerson) {
		InputStream inputStream = null;
		try {
			inputStream = file.getInputStream();
		} catch (IOException e) {
			log.error(ExceptionUtil.getRootCauseMessage(e));
		}
		return uploadFile(inputStream, fileType, file.getOriginalFilename(), table, field, remark, upPerson);
	}

	@Override
	public String uploadFile(File file, FileTypeEnum fileType, String table, String field, String remark, Long upPerson) {
		return uploadFile(FileUtil.readBytes(file), fileType, file.getName(), table, field, remark, upPerson);
	}

	@Override
	public String uploadFile(InputStream is, FileTypeEnum fileType, String fileName, String tableName, String field, String remark, Long uploadPerson) {
		return uploadFile(IoUtil.readBytes(is), fileType, fileName, tableName, field, remark, uploadPerson);
	}

	@Override
	public String uploadFile(byte[] data, FileTypeEnum fileType, String fileName, String table, String field, String remark, Long upPerson) {
		if (data.length == 0) {
			return null;
		}
		if (upPerson == null) {
			upPerson = 1L;
		}
		if (StringUtil.isBlank(table)) {
			table = "未知";
		}
		if (StringUtil.isBlank(field)) {
			field = "未知";
		}
		QNKeyEnum qnKeyEnum = getKeyEnumByFileType(fileType);
		// 检测资源是否非法，非法抛异常
		if (qnKeyEnum == QNKeyEnum.GENERAL_PICTURE_UPLOAD) {
			boolean normal = QiNiuUploadUtil.checkResourcesNormal(Base64.encode(data), true);
			if (!normal) {
				throw new BusinessException("图片可能涉嫌违规，请检查后重新上传!");
			}
		}
		try {
			// 文件名hash处理,防附件重复
			String fileKey = DigestUtil.md5Hex(data);
			if (StringUtil.isBlank(fileName)) {
				// 不传递文件名，默认将hash作为文件名
				fileName = fileKey;
			}
			QiNiuYunConfig qiNiuYunConfig = configCenterProperties.getQiNiuYunConfig();
			StringMap all = YamiUpPutPolicy.createPolicyParams(
					qiNiuYunConfig.getSystemMark() + appName,
					fileKey,
					table,
					field,
					String.valueOf(upPerson),
					DateUtil.now(),
					remark,
					fileName,
					String.valueOf(data.length)
			);
			// 如果是本地不需要七牛回调
			boolean callBack = !"local".equals(qiNiuYunConfig.getSystemMark());
			QNResponse response = QiNiuUploadUtil.upload(data, all, qnKeyEnum, callBack);
			// 如果不需要七牛回调，则业务插入
			if (!callBack) {
				this.fileSaveCallBack(response.getResponse().bodyString());
			}
			return fileKey;
		} catch (Exception e) {
			log.error(ExceptionUtil.getMessage(e));
			return null;
		}
	}

	@Override
	public Map<String, Integer> cleanUp(String clean) {
		Map<String, Integer> result = new HashMap<>();
		QiNiuYunConfig qiNiuYunConfig = configCenterProperties.getQiNiuYunConfig();
		//1.获取正在使用的文件
		Set<String> userFileList = new HashSet<>();
		Map<String, String> fileTableNames = getFileTableNames();
		if (fileTableNames.size() > 0) {
			fileTableNames.forEach((key, value) -> {
				String[] fieldNames = value.split(",");
				for (String filedName : fieldNames) {
					appendFile(userFileList, key, filedName);
				}
			});
		}
		// 2.查询附件表的所有文件ID
		List<FileInforEntity> fileInforEntitys = mapper.selectByExample(
				TK.select(FileInforEntity.class)
						.where()
						.andLike(FileInforEntity::getSysType, qiNiuYunConfig.getSystemMark() + "%").end()
		);
		// 3.要删除的文件ID集合
		List<String> deleteFileImg = new ArrayList<>();
		List<String> deleteFileDoc = new ArrayList<>();
		List<String> deleteFileRec = new ArrayList<>();
		List<String> deleteFileVideo = new ArrayList<>();
		for (FileInforEntity FileInforEntity : fileInforEntitys) {
			// 如果不使用。则加入删除列表
			if (!userFileList.contains(FileInforEntity.getId())) {
				switch (FileInforEntity.getFileType()) {
					case "IMG":
						deleteFileImg.add(FileInforEntity.getId());
						break;
					case "DOC":
						deleteFileDoc.add(FileInforEntity.getId());
						break;
					case "REC":
						deleteFileRec.add(FileInforEntity.getId());
						break;
					case "VIDEO":
						deleteFileVideo.add(FileInforEntity.getId());
						break;
					default:
						break;
				}
			}
		}
		String system = qiNiuYunConfig.getSystemMark();
		int count = deleteFile(deleteFileImg, system, qiNiuYunConfig.getImgBucket());
		result.put("图片", deleteFileImg.size());
		count += deleteFile(deleteFileDoc, system, qiNiuYunConfig.getDocBucket());
		result.put("文档", deleteFileDoc.size());
		count += deleteFile(deleteFileRec, system, qiNiuYunConfig.getVoiceBucket());
		result.put("语音", deleteFileRec.size());
		count += deleteFile(deleteFileVideo, system, qiNiuYunConfig.getVideoBucket());
		result.put("视频", deleteFileVideo.size());
		result.put("总计", count);
		return result;
	}

	/**
	 * 将正在使用的文件拼接起来
	 *
	 * @param souceStr
	 * @param table
	 * @param field
	 */
	private void appendFile(Set<String> souceStr, String table, String field) {
		StringBuilder stringBuffer = new StringBuilder(" select ");
		stringBuffer.append(field).append("  from ").append(table).append(" where ").append(field).append(" is not null");
		List<JSONObject> files = commonSV.queryListJson(stringBuffer.toString(), null);
		if (!files.isEmpty()) {
			StringBuilder fileStr = new StringBuilder();
			files.stream().forEach(n -> fileStr.append(n).append(","));
			//匹配32位连续的数字+字幕
			String reg = "(\\w{32})";
			Pattern pattern = Pattern.compile(reg);
			Matcher matcher = pattern.matcher(fileStr);
			while (matcher.find()) {
				souceStr.add(matcher.group(1));
			}
		}
	}

	/**
	 * 根据bucket删除文件
	 *
	 * @param deleteFileList
	 * @param bucket
	 */
	private int deleteFile(List<String> deleteFileList, String system, String bucket) {
		if (deleteFileList.isEmpty()) {
			log.info("{}无废文件删除", bucket);
			return 0;
		}
		List<String> successKey = new ArrayList<>();
		BatchStatus[] videos = QiNiuUploadUtil.delete(deleteFileList, system, bucket);
		for (int i = 0; i < deleteFileList.size(); i++) {
			BatchStatus status = videos[i];
			String key = deleteFileList.get(i);
			if (status.code == 200) {
				log.info("{}删除七牛文件成功：{}", bucket, key);
				successKey.add(key);
			} else {
				log.error("{}删除七牛文件{}异常:{}", bucket, key, status);
				successKey.add(key);
			}
		}
		if (!successKey.isEmpty()) {
			successKey = successKey.stream().map(n -> "'" + n + "'").collect(Collectors.toList());
			String deletedSql = " delete from file_infor where  id in ( ";
			for (int i = 0; i < successKey.size(); i++) {
				deletedSql += successKey.get(i);
				if (i != successKey.size() - 1) {
					deletedSql += ",";
				}
			}
			deletedSql += " ) ";
			commonSV.delete(deletedSql);
			log.info("{}文件清理结束，共清理无效文件{}个", bucket, successKey.size());
		}
		return successKey.size();
	}


	@Override
	public void fileSaveCallBack(String upPutPolicyStr) {
		GlobalThreadPool.execute(() -> {
			YamiUpPutPolicy upPutPolicy = JSONObject.parseObject(upPutPolicyStr, YamiUpPutPolicy.class);
			FileInforEntity FileInforEntity = new FileInforEntity();
			String key = upPutPolicy.getKey();
			String url = getShowUrlByFileType(upPutPolicy.getBucket());
			FileInforEntity.setId(key.substring(key.lastIndexOf("/") + 1, key.length()));
			FileInforEntity.setFileCate(upPutPolicy.getRemark());
			FileInforEntity.setFileType(getFileTypeByBucket(upPutPolicy.getBucket()));
			url += FileInforEntity.getId();
			// 文件大小设置
			if (StringUtils.isNotBlank(upPutPolicy.getSize()) && !"null".equals(upPutPolicy.getSize())) {
				FileInforEntity.setFileSize(Long.valueOf(upPutPolicy.getSize()));
			} else if (StringUtils.isNotBlank(upPutPolicy.getFileSize()) && !"null".equals(upPutPolicy.getFileSize())) {
				FileInforEntity.setFileSize(Long.valueOf(upPutPolicy.getFileSize()));
			}
			// 后缀设置
			String ext = upPutPolicy.getExt();
			if (StringUtils.isNotBlank(ext) && ext.contains(".")) {
				FileInforEntity.setFileExt(ext.substring(ext.indexOf(".") + 1, ext.length()));
			} else if (StringUtils.isNotBlank(upPutPolicy.getFileExt()) && !"null".equals(upPutPolicy.getFileExt())) {
				FileInforEntity.setFileExt(upPutPolicy.getFileExt());
			}
			// 文件名设置
			if (StringUtils.isNotBlank(upPutPolicy.getFileName()) && !"null".equals(upPutPolicy.getFileName())) {
				if (!upPutPolicy.getFileName().contains(".")) {
					FileInforEntity.setFileName(upPutPolicy.getFileName() + "." + upPutPolicy.getFileExt());
				} else {
					FileInforEntity.setFileName(upPutPolicy.getFileName());
				}
			}
			FileInforEntity.setFilePath(key);
			FileInforEntity.setSysType(upPutPolicy.getSystem());
			if (StringUtils.isNotBlank(upPutPolicy.getField()) && !"null".equals(upPutPolicy.getField())) {
				FileInforEntity.setUserTable(upPutPolicy.getTable() + "_" + upPutPolicy.getField());
			} else {
				FileInforEntity.setUserTable(upPutPolicy.getTable());
			}
			if (StringUtils.isNotBlank(upPutPolicy.getUpPerson()) && !"null".equals(upPutPolicy.getUpPerson())) {
				FileInforEntity.setUploaderId(Long.valueOf(upPutPolicy.getUpPerson()));
			}
			FileInforEntity.setUploadTime(new Date());
			// 附件额外参数设置，云信使用
			JSONObject extra = new JSONObject();
			if (StringUtils.isNotBlank(upPutPolicy.getDuration()) && !"null".equals(upPutPolicy.getDuration())) {
				extra.put("dur", Double.valueOf(upPutPolicy.getDuration()) * 1000);
			}
			extra.put("model", FileInforEntity.getFileExt());
			extra.put("size", FileInforEntity.getFileSize());
			extra.put("name", FileInforEntity.getFileName());
			extra.put("md5", upPutPolicy.getHashKey());
			extra.put("w", upPutPolicy.getWidth());
			extra.put("h", upPutPolicy.getHeight());
			extra.put("url", url);
			FileInforEntity.setExtra(extra.toJSONString());
			try {
				mapper.insertSelective(FileInforEntity);
			} catch (DuplicateKeyException e) {
				log.info("重复上传附件:{}", key);
			}
		});
	}

	/**
	 * 获取配置的附件关键系表名
	 */
	private Map<String, String> getFileTableNames() {
		Map<String, String> mapping = new HashMap<>();
		List<String> tablesConfig = fileTableConfig.getTables();
		List<Map<String, String>> collect = tablesConfig.stream().map(n -> {
			Map<String, String> table = new HashMap<>();
			String[] split = n.split("\\|");
			table.put(split[0], split[1]);
			return table;
		}).collect(Collectors.toList());
		for (Map<String, String> stringStringMap : collect) {
			mapping.putAll(stringStringMap);
		}
		return mapping;
	}

	@Override
	public Map<String, Integer> copyFileToTargetEnv(String tables, String env) {
		Map<String, Integer> result = new HashMap<>();
		QiNiuYunConfig qiNiuYunConfig = configCenterProperties.getQiNiuYunConfig();
		// 1.查询附件表的所有文件ID
		List<FileInforEntity> FileInforEntitys = new ArrayList<>();
		if (StringUtil.isBlank(tables)) {
			// 如果不传递表名，默认全部表
			String allFileSql = "select id, file_type from file_infor where sys_type like '" + qiNiuYunConfig.getSystemMark() + "%'";
			FileInforEntitys = commonSV.queryListEntity(allFileSql, null, FileInforEntity.class);
		} else {
			// 否则拷贝部分表
			Set<String> fileKeys = new HashSet<>();
			Map<String, String> fileTableNames = getFileTableNames();
			String[] splitTables = tables.split(",");
			for (String splitTable : splitTables) {
				if (fileTableNames.containsKey(splitTable)) {
					String[] fieldNames = fileTableNames.get(splitTable).split(",");
					for (String filedName : fieldNames) {
						appendFile(fileKeys, splitTable, filedName);
					}
				}
			}
			// 查询要copy的附件信息
			if (fileKeys.size() > 0) {
				String sql = " select * from file_infor where  id in ( ";
				for (String fileKey : fileKeys) {
					sql += "'" + fileKey + "',";

				}
				sql = sql.substring(0, sql.length() - 1);
				sql += " ) ";
				FileInforEntitys = commonSV.queryListEntity(sql, null, FileInforEntity.class);
			}
		}

		if (FileInforEntitys.size() > 0) {
			List<String> imgFiles = FileInforEntitys.stream().filter(n -> "IMG".equals(n.getFileType())).map(n -> n.getId()).collect(Collectors.toList());
			List<String> docFiles = FileInforEntitys.stream().filter(n -> "DOC".equals(n.getFileType())).map(n -> n.getId()).collect(Collectors.toList());
			List<String> recFiles = FileInforEntitys.stream().filter(n -> "REC".equals(n.getFileType())).map(n -> n.getId()).collect(Collectors.toList());
			List<String> videoFiles = FileInforEntitys.stream().filter(n -> "VIDEO".equals(n.getFileType())).map(n -> n.getId()).collect(Collectors.toList());
			// 列出该资源所有的key
			result.put("计划拷贝图片", imgFiles.size());
			int success = QiNiuUploadUtil.copy(qiNiuYunConfig.getSystemMark(), "images", imgFiles, env, false);
			result.put("拷贝图片成功", success);

			result.put("计划拷贝文档", docFiles.size());
			success = QiNiuUploadUtil.copy(qiNiuYunConfig.getSystemMark(), "documents", docFiles, env, false);
			result.put("拷贝文档成功", success);

			result.put("计划拷贝语音", recFiles.size());
			success = QiNiuUploadUtil.copy(qiNiuYunConfig.getSystemMark(), "voices", recFiles, env, false);
			result.put("拷贝语音成功", success);

			result.put("计划拷贝视频", videoFiles.size());
			success = QiNiuUploadUtil.copy(qiNiuYunConfig.getSystemMark(), "vidoes", videoFiles, env, false);
			result.put("拷贝视频成功", success);
		}
		return result;
	}


	/**
	 * 根据fileType获取上传的文件枚举
	 *
	 * @param fileType
	 * @return
	 */
	@Override
	public QNKeyEnum getKeyEnumByFileType(FileTypeEnum fileType) {
		QNKeyEnum qnKeyEnum = QNKeyEnum.GENERAL_PICTURE_UPLOAD;
		if (FileTypeEnum.DOC == fileType) {
			qnKeyEnum = QNKeyEnum.GENERAL_DOC_UPLOAD;
		} else if (FileTypeEnum.IMG == fileType) {
			qnKeyEnum = QNKeyEnum.GENERAL_PICTURE_UPLOAD;
		} else if (FileTypeEnum.REC == fileType) {
			qnKeyEnum = QNKeyEnum.GENERAL_REC_UPLOAD;
		} else if (FileTypeEnum.VIDEO == fileType) {
			qnKeyEnum = QNKeyEnum.GENERAL_VIDEO_UPLOAD;
		}
		return qnKeyEnum;
	}

	/**
	 * 根据文件类型，获取预览地址
	 *
	 * @param fileType
	 * @return
	 */
	@Override
	public String getShowUrlByFileType(String fileType) {
		QiNiuYunConfig qiNiuYunConfig = configCenterProperties.getQiNiuYunConfig();
		String url = null;
		if (FileTypeEnum.DOC.getKey().equals(fileType) || "documents".equals(fileType)) {
			url = qiNiuYunConfig.getShowDocUrl();
		} else if (FileTypeEnum.IMG.getKey().equals(fileType) || "images".equals(fileType)) {
			url = qiNiuYunConfig.getShowImageUrl();
		} else if (FileTypeEnum.REC.getKey().equals(fileType) || "voices".equals(fileType)) {
			url = qiNiuYunConfig.getShowRecUrl();
		} else if (FileTypeEnum.VIDEO.getKey().equals(fileType) || "videos".equals(fileType)) {
			url = qiNiuYunConfig.getShowVideoUrl();
		}
		return url;
	}

	/**
	 * 根据bucket获取文件类型
	 *
	 * @return
	 */
	public String getFileTypeByBucket(String bucket) {
		String fileType = null;
		if ("documents".equals(bucket)) {
			fileType = FileTypeEnum.DOC.getKey();
		} else if ("images".equals(bucket)) {
			fileType = FileTypeEnum.IMG.getKey();
		} else if ("voices".equals(bucket)) {
			fileType = FileTypeEnum.REC.getKey();
		} else if ("videos".equals(bucket)) {
			fileType = FileTypeEnum.VIDEO.getKey();
		}
		return fileType;
	}

}
